#!/usr/bin/env python

# Copyright (c) 2009 Peter Corbett
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.

import pgcurses
import gamestate
import data
import renderer
import los
import utils
import magic
import mobai

import time
import cPickle
import sys
import random
import re
import textwrap
import gzip

class Prl:
    def __init__(self):
        pass
    def main(self):
        self.pgc = pgcurses.PGCurses(80,25)
        self.data = data.Data()
        self.renderer = renderer.Renderer(self.pgc, self.data)
        self.los = los.LOS()
        c = self.renderer.select_class(self.data)
        self.state = gamestate.GameState(self.data, c)
        self.magic = magic.Magic(self, self.data, self.state)
        self.mobai = mobai.MobAI(self, self.data, self.state)
        self.mainloop()
    def mainloop(self):
        self.state.lostab = self.los.losmap(self.state.current_level, self.data, self.state.pc.x, self.state.pc.y)
        if "blind" not in self.state.pc.statuses:
            self.state.current_level.learn_los(self.state.lostab)
        self.current_message = None
        self.renderer.render(self.state)
        self.renderer.refresh()
        self.add_message("Welcome to Lord Midwinter's Realm!")
        self.renderer.render_message(self.current_message)
        self.renderer.render(self.state)
        self.renderer.refresh()
        self.current_message = None
        while self.state.is_playing():
            if self.state.running:
                self.handle_running()
            elif self.state.resting:
                self.handle_resting()
            elif "sleep" in self.state.pc.statuses:
                self.idle()
            else:
                command = self.pgc.getch()
                if command == "Q":
                    self.state.is_alive = False
                elif command == "W":
                    self.state.is_victorious = True
                elif command == "?":
                    self.renderer.render_text_file("help.txt")
                elif command == "i":
                    self.inventory()
                elif command == "g":
                    self.get_item()
                elif command == "d":
                    self.drop_item()
                elif command == "w":
                    self.wield_item()
                elif command == "t":
                    self.take_off_item()
                elif command == "l":
                    self.look_mode()
                elif command == "S":
                    self.save()
                    self.add_message("Saved OK")
                elif command == "L":
                    self.load()
                    self.add_message("Loaded OK")
                elif command == "M":
                    self.renderer.message_history(self.state.messages)
                elif command == "m":
                    self.cast_spell()
                elif command == "c":
                    self.cheat()
                elif command == "UP" or command == "KP8":
                    self.move_pc(0, -1)
                elif command == "DOWN" or command == "KP2":
                    self.move_pc(0, 1)
                elif command == "LEFT" or command == "KP4":
                    self.move_pc(-1, 0)
                elif command == "RIGHT" or command == "KP6":
                    self.move_pc(1, 0)
                elif command == "KP1":
                    self.move_pc(-1, 1)
                elif command == "KP3":
                    self.move_pc(1, 1)
                elif command == "KP7":
                    self.move_pc(-1, -1)
                elif command == "KP9":
                    self.move_pc(1, -1)
                elif command == "KP0":
                    self.idle()
                elif command == "<":
                    self.change_level(-1)
                elif command == ">":
                    self.change_level(1)
                elif command == "KP5":
                    self.set_running()
                elif command == "C":
                    self.do_char_screen()
                elif command == "q":
                    self.use_item("quaff")
                elif command == "r":
                    self.use_item("read")
                elif command == "f":
                    self.fire()
                elif command == "R":
                    self.set_resting()
            # handle commands
            if not self.state.resting:
                self.state.lostab = self.los.losmap(self.state.current_level, self.data, self.state.pc.x, self.state.pc.y)
                if "blind" not in self.state.pc.statuses:
                    self.state.current_level.learn_los(self.state.lostab)
                if self.state.is_playing():
                    self.renderer.render_message(self.current_message)
                self.renderer.render(self.state)
                self.renderer.refresh()
            # mob actions
            if self.state.pc.time > 0:
                self.state.pc.regen(self.state.pc.time, self.data)
                if self.state.pc.hp <= 0:
                    self.kill_mob(self.state.pc)
                else:
                    t = self.state.pc.time
                    smobs = sorted(self.state.current_level.mobs,
                               lambda a, b: cmp(a.time, b.time))
                    for mob in smobs:
                        mob.time -= t
                        mob.regen(t, self.data)
                        if mob.hp <= 0:
                            self.kill_mob(mob)
                            self.add_message("%s dies!" % mob.desc(self.data, "the", True))
                        else:
                            if mob.can_see(self.state.pc, self.state, self.data):
                                mob.aidict["last_pc_loc"] = (self.state.pc.x, self.state.pc.y)
                            if mob.initiative == 1:
                                mob.time = min(-1, mob.time)
                                mob.initiative = 0
                            if mob.initiative == -1:
                                mob.time = max(1, mob.time)
                                mob.initiative = 0
                            while mob.time < 0:
                                mob.spot_check(self.state.pc, self.state, self.data, self)
                                self.state.pc.spot_check(mob, self.state, self.data, self)
                                self.mobai.do_mob_move(mob)
                                if mob.can_see(self.state.pc, self.state, self.data):
                                    mob.aidict["last_pc_loc"] = (self.state.pc.x, self.state.pc.y)
                                if mob.initiative == 1:
                                    mob.time = min(-1, mob.time)
                                    mob.initiative = 0
                                if mob.initiative == -1:
                                    mob.time = max(1, mob.time)
                                    mob.initiative = 0
                self.state.pc.time = 0
            if not self.state.resting:
                self.state.lostab = self.los.losmap(self.state.current_level, self.data, self.state.pc.x, self.state.pc.y)
                if "blind" not in self.state.pc.statuses:
                    self.state.current_level.learn_los(self.state.lostab)
            if self.state.is_playing() and not self.state.resting:
                self.renderer.render_message(self.current_message)
            if not self.state.running and not self.state.resting:
                self.renderer.render(self.state)
                self.renderer.refresh()
            if self.state.is_playing(): self.current_message = None

        if self.state.is_victorious:
            self.add_message("You win! [Press any key]", False)
            self.renderer.render_message(self.current_message)
            self.pgc.refresh()
            self.pgc.getch()
        elif not self.state.is_alive:
            self.add_message("You die! [Press any key]", False)
            self.renderer.render_message(self.current_message)
            self.pgc.refresh()
            self.pgc.getch()
    def cheat(self):
        self.add_message("What cheat command?", False)
        self.renderer.render_message(self.current_message)
        self.pgc.refresh()
        self.current_message = None
        command = self.pgc.getch()
        if command == "o":
            self.state.cheat_omniscent = not self.state.cheat_omniscent
        elif command == "<":
            self.change_level(-1, False)
        elif command == ">":
            self.change_level(1, False)
        elif command == "t":
            self.teleport()
        elif command == "w":
            self.wish()
        elif command == "i":
            self.state.pc.inv.append(gamestate.Item(self.data.itemdict["pair of leather boots"]))
            self.state.pc.sortinv(self.data)
        elif command == "r":
            pc = self.state.pc
            pc.hp = pc.maxhp
            pc.mp = pc.maxmp
        elif command == "l":
            self.level_up()
        elif command == "x":
            self.gain_xp(1)
    def check_for_mobs(self):
        for m in self.state.current_level.mobs:
            if self.state.pc.can_see(m, self.state, self.data): return True
        return False
    def set_resting(self):
        pc = self.state.pc
        r = {}
        if pc.hp < pc.maxhp:
            r["hp"] = pc.hp
        if pc.mp < pc.maxmp:
            r["mp"] = pc.mp
        if r:
            self.state.resting = r
        else:
            self.add_message("You are already well-rested.")
    def handle_resting(self):
        if (self.check_for_mobs() or
            ("hp" in self.state.resting and self.state.pc.hp == self.state.pc.maxhp) or
            ("mp" in self.state.resting and self.state.pc.mp == self.state.pc.maxmp)):
            self.state.resting = None
        else:
            self.state.pc.time += self.state.pc.movtime
            change = False
            if "hp" in self.state.resting and self.state.resting["hp"] != self.state.pc.hp:
                change = True
                self.state.resting["hp"] = self.state.pc.hp
            if "mp" in self.state.resting and self.state.resting["mp"] != self.state.pc.mp:
                change = True
                self.state.resting["mp"] = self.state.pc.mp
            if change:
                self.renderer.render(self.state)
                self.renderer.refresh()
    def set_running(self):
        self.add_message("Run which way?")
        self.renderer.render_message(self.current_message)
        self.pgc.refresh()
        while self.state.running is None:
            command = self.pgc.getch()
            if command == "ESC":
                break
            elif command == "UP" or command == "KP8":
                self.state.running = (0, -1)
            elif command == "DOWN" or command == "KP2":
                self.state.running = (0, 1)
            elif command == "LEFT" or command == "KP4":
                self.state.running = (-1, 0)
            elif command == "RIGHT" or command == "KP6":
                self.state.running = (1, 0)
            elif command == "KP1":
                self.state.running = (-1, 1)
            elif command == "KP3":
                self.state.running = (1, 1)
            elif command == "KP7":
                self.state.running = (-1, -1)
            elif command == "KP9":
                self.state.running = (1, -1)
        if self.state.running:
            self.handle_running(True)
    def handle_running(self, launch=False):
        if not launch and (
            self.check_for_mobs() or
            self.state.current_level.items[self.state.pc.x][self.state.pc.y]):
            self.state.running = None
            self.describe_tile_items(self.state.pc.x, self.state.pc.y)
            return
        dx, dy = self.state.running
        tx = self.state.pc.x + dx
        ty = self.state.pc.y + dy
        passable = self.state.current_level.passable(tx, ty, self.data, self.state.pc) == True
        nd1 = utils.rotate(self.state.running, 2)
        nd2 = utils.rotate(self.state.running, -2)
        nd1p = self.state.current_level.passable(self.state.pc.x + nd1[0],
                                                 self.state.pc.y + nd1[1],
                                                 self.data,
                                                 self.state.pc) == True
        nd2p = self.state.current_level.passable(self.state.pc.x + nd2[0],
                                                 self.state.pc.y + nd2[1],
                                                 self.data,
                                                 self.state.pc) == True
        if passable == True:
            nnd1p = self.state.current_level.passable(tx + nd1[0],
                                                 ty + nd1[1],
                                                 self.data,
                                                 self.state.pc) == True
            nnd2p = self.state.current_level.passable(tx + nd2[0],
                                                 ty + nd2[1],
                                                 self.data,
                                                 self.state.pc) == True
            rdp = self.state.current_level.passable(tx + self.state.running[0],
                                                 ty + self.state.running[1],
                                                 self.data,
                                                 self.state.pc) == True
            t1 = 1 + nd1p + nd2p
            t2 = nnd1p + nnd2p + rdp
            
            if launch or t1 == t2 or (t1 - 1 == t2 - rdp):
                self.state.pc.move_to((tx, ty), self.state, self.data)
                #self.state.pc.x = tx
                #self.state.pc.y = ty
                self.state.pc.time += self.state.pc.movtime
            else:
                self.state.running = None
        else:
            if nd1p and not nd2p:
                dd = utils.rotate(self.state.running, 3)
                if self.state.current_level.passable(self.state.pc.x + dd[0],
                                                     self.state.pc.y + dd[1],
                                                     self.data,
                                                     self.state.pc) == False:
                    self.state.running = nd1
                    self.handle_running(True)
                else:
                    self.state.running = None
            elif nd2p and not nd1p:
                dd = utils.rotate(self.state.running, -3)
                if self.state.current_level.passable(self.state.pc.x + dd[0],
                                                     self.state.pc.y + dd[1],
                                                     self.data,
                                                     self.state.pc) == False:
                    self.state.running = nd2
                    self.handle_running(True)
                else:
                    self.state.running = None
            else:
                self.state.running = None
    def idle(self):
        self.state.pc.time += self.state.pc.movtime
    def move_pc(self, dx, dy):
        if "confused" in self.state.pc.statuses and random.random() < 0.75:
            dx, dy = random.choice(((-1,-1),(-1,0),(-1,1),(0,-1),(0,1),(1,-1),(1,0),(1,1)))
        tx = self.state.pc.x + dx
        ty = self.state.pc.y + dy
        passable = self.state.current_level.passable(tx, ty, self.data, self.state.pc)
        if passable == False:
            self.add_message("Ouch!")
        elif passable == True:
            self.state.pc.move_to((tx, ty), self.state, self.data)
            #self.state.pc.x = tx
            #self.state.pc.y = ty
            self.state.pc.time += self.state.pc.movtime
            self.describe_tile_items(self.state.pc.x, self.state.pc.y)
        else:
            mob = passable
            if "stealth" in mob.statuses:
                mob.del_status("stealth", self.data)
                self.add_message("You spot %s!" % mob.desc(self.data, "the", False))
                self.state.pc.time += self.state.pc.atktime
            elif "fear" in self.state.pc.statuses:
                self.add_message("You are too afraid to attack!");
            else:
                self.resolve_attack(self.state.pc, mob)
                self.state.pc.time += self.state.pc.atktime
    def cast_spell(self):
        spell = self.select_spell()
        if spell is not None:
            stype = self.data.spelltypes[spell]
            if stype.mana > self.state.pc.mp:
                self.add_message("Not enough mana to cast %s." % stype.name)
                return
            self.state.pc.del_status("stealth", self.data)
            self.state.pc.mp -= stype.mana
            self.add_message("You cast %s." % stype.name)
            self.magic.cast(self.state.pc, self.state.pc.sdict["magpow"], stype)
            self.state.pc.time += self.state.pc.casttime
    def resolve_attack(self, attacker, defender):
        self.state.running = None
        self.state.resting = None
        defc = defender.sdict["hthdef"]
        hitc = attacker.sdict["hthhit"]
        atkpow = attacker.sdict["hthpow"]
        if "stealth" in attacker.statuses:
            attacker.del_status("stealth", self.data)
            atkpow *= 2
            self.add_message("Sneak attack!")
            defc = 0
        if random.randint(0, 100) < (defc - hitc):
            self.report_svo(attacker, "miss", defender)
        else:
            atkpow = random.randint(1, atkpow)
            self.resolve_hit(attacker, defender, atkpow)
    def resolve_hit(self, attacker, defender, power, ignore_armour=False):
        self.state.running = None
        self.state.resting = None
        defender.del_status("sleep", self.data)
        defender.del_status("stealth", self.data)
        atkpow = random.randint(1, max(1, power))
        arm = 0 if ignore_armour else defender.sdict["armour"]
        atkpow = max(1, (atkpow - arm) + min(atkpow, arm) / 2)
        #print atkpow
        if defender.hp <= atkpow:
            defender.hp = 0
            if attacker == self.state.pc:
                self.state.damage_indicator = None
            self.report_svo(attacker, "kill", defender)
            self.kill_mob(defender)
        else:
            self.report_svo(attacker, "hit", defender)
            defender.hp -= atkpow
            if attacker == self.state.pc:
                self.state.damage_indicator = defender.id
            if defender.moralecheck(atkpow, self.data):
                if defender == self.state.pc:
                    self.add_message("You fear for your life!")
                else:
                    self.add_message("%s turns to flee!" % defender.desc(self.data, "the", True))
    def report_svo(self, s, v, o):
        maybes = ""
        if not s == self.state.pc:
            maybes = "es" if v.endswith("s") else "s"
        self.add_message("%s %s%s %s!" % (s.desc(self.data, "the", True), v, maybes, o.desc(self.data, "the", False)))
    def kill_mob(self, mob):
        if mob is self.state.pc:
            self.state.is_alive = False
        else:
            self.state.current_level.mobs.remove(mob)
            if mob.au > 0:
                gold = gamestate.Item(self.data.itemdict["gold coin"], mob.au)
                self.state.current_level.add_item(mob.x, mob.y, gold)
            for i in mob.inv:
                i.slot = None
            self.state.current_level.add_items(mob.x, mob.y, mob.inv)
            self.gain_xp(mob.level)
            if self.data.mobtypes[mob.type].victory:
                self.state.is_victorious = True
    def change_level(self, direction, check=True):
        sym = ("X", ">", "<")[direction]
        if check and self.data.floortiles[self.state.current_level.floortiles[self.state.pc.x][self.state.pc.y]].ch != sym:
            self.add_message("You can't go %s there!" % ("", "down", "up")[direction])
            return
        if self.state.level + direction < 0:
            self.add_message("No returning to the surface, coward!")
            return
        self.state.level += direction
        if self.state.levels[self.state.level] == None:
            self.state.levels[self.state.level] = gamestate.Level(self.state, self.data, self.state.level)
        self.state.current_level = self.state.levels[self.state.level]
        if direction == 1:
            self.state.pc.move_to(self.state.current_level.stairs_up[0], self.state, self.data)
            #self.state.pc.x, self.state.pc.y = self.state.current_level.stairs_up[0]
        else:
            self.state.pc.move_to(self.state.current_level.stairs_down[0], self.state, self.data)
            #self.state.pc.x, self.state.pc.y = self.state.current_level.stairs_down[0]
    def describe_tile_items(self, x, y):
        items = self.state.current_level.items[x][y]
        for item in reversed(items):
            self.add_message("You see %s." % item.desc(self.data, "a"))
    def inventory(self):
        if len(self.state.pc.inv) == 0:
            self.add_message("You have no items!")
        else:
            self.renderer.render_message("Viewing inventory. [SPACE to continue]")
            self.renderer.render_inv(self.state.pc.inv, self.state.pc.slots)
            self.renderer.refresh()
            while 1:
                ch = self.pgc.getch()
                if ch == " ":
                    break
    def get_item(self):
        items = self.state.current_level.items[self.state.pc.x][self.state.pc.y]
        if len(items) == 0:
            self.add_message("There's nothing there to pick up!")
        elif len(self.state.pc.inv) >= 36:
            self.add_message("Your pack is full!")
        else:
            item = items.pop()
            itemtype = self.data.itemtypes[item.type]        
            self.add_message("You get %s." % item.desc(self.data, "the"))
            if itemtype.name == "gold coin":
                self.state.pc.au += item.number
            else:
                self.state.pc.inv.append(item)
            self.state.pc.sortinv(self.data)
            self.describe_tile_items(self.state.pc.x, self.state.pc.y)
            self.state.pc.time += self.state.pc.acttime
    def drop_item(self):
        itemno = self.select_item("Select item to drop. [ESC to cancel]")
        if itemno == -1:
            self.add_message("You have nothing you can drop!")  
        elif itemno is not None:
            item = self.state.pc.inv[itemno]
            itemtype = self.data.itemtypes[item.type]
            n = item.number
            if n > 1:
                dn = self.renderer.ask_for_number("Drop how many %s?" % item.desc(self.data, ""), 7, 0, 0, n, n)
                if dn == 0:
                    return
                elif dn < n:
                    item = item.split(dn)
                else:
                    self.state.pc.inv.pop(itemno)
            else:
                self.state.pc.inv.pop(itemno)
            slot = item.slot
            if slot is not None:
                del self.state.pc.slots[slot]
                item.slot = None
            self.add_message("You drop %s." % item.desc(self.data, "the"))
            self.state.current_level.add_item(self.state.pc.x, self.state.pc.y, item)
            self.state.pc.sortinv(self.data)
            self.state.pc.calc_stats(self.data)
            self.state.pc.time += self.state.pc.acttime
    def wield_item(self):
        itemno = self.select_item("Select item to wield. [ESC to cancel]", lambda i, t: t.slots in self.data.slotdict and not i.slot)
        if itemno == -1:
            self.add_message("You have nothing you can wield!")  
        elif itemno is not None:
            item = self.state.pc.inv[itemno]
            if self.state.pc.wield_item(item, self.data):
                self.add_message("You %s %s." % (self.data.slottypes[self.data.slotdict[item.slot]].verb, item.desc(self.data, "the")))
                self.state.pc.calc_stats(self.data)
                self.state.pc.time += self.state.pc.acttime
            else:
                self.add_message("You can't wield that!")
    def take_off_item(self):
        itemno = self.select_item("Select item to take off. [ESC to cancel]", lambda i, t: i.slot)
        if itemno == -1:
            self.add_message("You have nothing you can take off!")  
        elif itemno is not None:
            item = self.state.pc.inv[itemno]
            del self.state.pc.slots[item.slot]
            item.slot = None
            self.state.pc.time += self.state.pc.acttime
            self.add_message("You take off %s." % item.desc(self.data, "the"))
            self.state.pc.time += self.state.pc.acttime
    def fire(self):
        maybeslot = [i for i in ("thrown", "slingstone", "arrow") if i in self.state.pc.slots]
        if not maybeslot:
            self.add_message("You don't have anything ready to fire!")
        else:
            target = self.target(True, "Select target: ")
            if target is not None:
                p = self.state.current_level.passable(target[0], target[1], self.data, self.state.pc)
                if p is False or p is self.state.pc:
                    self.add_message("You can't shoot there!")
                else:
                    self.state.pc.del_status("stealth", self.data)
                    item = self.state.pc.slots[maybeslot[0]]
                    if item.number > 1:
                        item = item.split(1)
                        item.slot = None
                    else:
                        item.slot = None
                        del self.state.pc.slots[maybeslot[0]]
                        del self.state.pc.inv[self.state.pc.inv.index(item)]
                    self.state.current_level.add_item(target[0], target[1], item)
                    if isinstance(p, gamestate.Mob):
                        defc = p.sdict["misdef"]
                        hitc = self.state.pc.sdict["mishit"]
                        if random.randint(0, 100) < (defc - hitc - self.state.pc.range_to(p)):
                            self.report_svo(attacker, "miss", defender)
                        else:
                            self.resolve_hit(self.state.pc, p, self.state.pc.sdict["mispow"])
                    self.state.pc.sortinv(self.data)
                    self.state.pc.calc_stats(self.data)
                    self.state.pc.time += self.state.pc.matktime
    def use_item(self, action):
        itemno = self.select_item("Select item to %s. [ESC to cancel]" % action, lambda i, t: t.slots == action)
        if itemno == -1:
            self.add_message("You have nothing you can %s!" % action)  
        elif itemno is not None:
            item = self.state.pc.inv[itemno]
            itemtype = self.data.itemtypes[item.type]
            self.state.pc.del_status("stealth", self.data)
            self.add_message("You %s %s." % (action, item.desc(self.data, "the", True)))
            if "effect" in itemtype.dict:
                effect = itemtype.dict["effect"]
                spell = self.data.spelldict[effect]
                stype = self.data.spelltypes[spell]
                self.magic.cast(self.state.pc, 10, stype)
            if item.number > 1:
                item.number -= 1
            else:
                del self.state.pc.inv[itemno]
            self.state.pc.sortinv(self.data)
            self.state.pc.calc_stats(self.data)
            self.state.pc.time += self.state.pc.acttime
    def select_item(self, message, test=None):
        if len(self.state.pc.inv) == 0:
            self.add_message("You have no items!")
            return None
        elif len([i for i in self.state.pc.inv if not test or test(i, self.data.itemtypes[i.type])]) == 0 :
            return -1
        else:
            self.renderer.render_message(message)
            self.renderer.render_inv(self.state.pc.inv, self.state.pc.slots, test)
            self.renderer.refresh()
            chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
            while 1:
                ch = self.pgc.getch()
                if ch == "ESC":
                    return None
                chi = chars.find(ch)
                if chi == -1 or chi >= len(self.state.pc.inv) or (test and not test(self.state.pc.inv[chi], self.data.itemtypes[self.state.pc.inv[chi].type])):
                    continue
                return chi
    def select_spell(self):
        if len(self.state.pc.spells) == 0:
            self.add_message("You have no spells!")
            return None
        else:
            self.renderer.render_message("Select a spell [ESCAPE to abort]")
            self.renderer.render_spells(self.state.pc.spells)
            self.renderer.refresh()
            chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
            while 1:
                ch = self.pgc.getch()
                if ch == "ESC":
                    return None
                chi = chars.find(ch)
                if chi == -1 or chi >= len(self.state.pc.spells):
                    continue
                return self.state.pc.spells[chi]
    def look_mode(self):
        self.target()
    def teleport(self):
        t = self.target()
        if t:
            self.state.pc.move_to(t, self.state, self.data)
    def wish(self):
        s = self.renderer.ask_for_string("Wish for what item?")
        s = s.strip()
        n = 1
        m = re.match("(\\d+) (.+)", s)
        if m:
            n = int(m.group(1))
            s = m.group(2)
        if s in self.data.itemdict:
            self.state.pc.inv.append(gamestate.Item(self.data.itemdict[s], n))
            self.state.pc.sortinv(self.data)
            self.state.pc.calc_stats(self.data)
    def target(self, constrain_to_los=False, prefix=""):
        x = self.state.pc.x
        y = self.state.pc.y
        message = None
        while 1:
            mobat = None
            message = None
            verb = "see" if self.state.lostab[x][y] else "remember"
            for m in self.state.current_level.mobs:
                if m.x == x and m.y == y:
                    mobat = m
            if x == self.state.pc.x and y == self.state.pc.y:
                message = "You see yourself."
            elif mobat != None and self.state.pc.can_see(mobat, self.state, self.data):
                message = "You see %s." % mobat.desc(self.data, "a", False)
            elif self.state.current_level.items[x][y] and self.state.current_level.known[x][y]:
                items = self.state.current_level.items[x][y]
                if len(items) > 1:
                    message = "You %s a pile of items." % verb
                else:
                    message = "You %s %s." % (verb, items[0].desc(self.data, "a"))
            elif self.state.current_level.known[x][y]:
                message = "You %s a %s." % (verb, self.data.floortiles[self.state.current_level.floortiles[x][y]].name)
            else:
                message = "You see nothing."

            #self.renderer.render(self.state)
            self.renderer.render_level(self.state.current_level, self.state, self.state.lostab, self.state.cheat_omniscent, (x, y))
            self.renderer.render_message(prefix + message)
            self.renderer.refresh()
            ch = self.pgc.getch()
            tx = x
            ty = y
            if ch == "ESC":
                return None
            elif ch == "t":
                return (x, y)
            elif ch == "KP1":
                tx -= 1
                ty += 1
            elif ch == "KP2" or ch == "DOWN":
                ty += 1
            elif ch == "KP3":
                tx += 1
                ty += 1
            elif ch == "KP4" or ch == "LEFT":
                tx -= 1
            elif ch == "KP6" or ch == "RIGHT":
                tx += 1
            elif ch == "KP7":
                tx -= 1
                ty -= 1
            elif ch == "KP8" or ch == "UP":
                ty -= 1
            elif ch == "KP9":
                tx += 1
                ty -= 1
            tx = max(min(79, tx), 0)
            ty = max(min(19, ty), 0)
            if not constrain_to_los or self.state.lostab[tx][ty]:
                x, y = tx, ty            
    def reveal(self):
        for x in range(80):
            for y in range(20):
                self.state.current_level.known[x][y] = True
    def do_char_screen(self):
        self.renderer.render_character(self.state.pc, self.data)
        self.renderer.render_message("Viewing character [Press any key]")
        self.renderer.refresh()
        self.pgc.getch()
    def gain_xp(self, xp):
        oldxp = self.state.xp
        self.state.xp += xp
        nlx = (self.state.pc.level * (self.state.pc.level + 1)) * 5
        while self.state.xp >= nlx:
            self.level_up()
            nlx = (self.state.pc.level * (self.state.pc.level + 1)) * 5
    def level_up(self):
        self.state.pc.level += 1
        self.state.pc.calc_stats(self.data)
        self.add_message("Welcome to level %s." % self.state.pc.level)
        allowable_spells = min(36, (self.state.pc.level * self.state.pc.int) / 10)
        while len(self.state.pc.spells) < allowable_spells:
            self.renderer.render_message("Select a spell/ability to learn:")
            learnable = [i for i in range(len(self.data.spelltypes)) if i not in self.state.pc.spells]
            if len(learnable) == 0:
                break
            self.renderer.render_spells(learnable)
            self.renderer.refresh()
            chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
            while 1:
                ch = self.pgc.getch()
                chi = chars.find(ch)
                if chi == -1 or chi >= len(learnable):
                    continue
                self.state.pc.spells.append(learnable[chi])
                break
    def save(self):
        f = gzip.GzipFile("save.sav", "w")
        #f = open("save.sav", "w")
        cPickle.dump(self.state, f)
        f.close()
    def load(self):
        f = gzip.GzipFile("save.sav", "r")
        #f = open("save.sav", "r")
        self.state = cPickle.load(f)
        self.magic.state = self.state
        self.mobai.state = self.state
    def add_message(self, message, permanent=True):
        message = message.strip()
        if "\n" in message:
            for m in message.split("\n"):
                self.add_message(m, False)
        elif len(message) > 70:
            for m in textwrap.wrap(message, 70):
                self.add_message(m, False)
        elif self.current_message is None:
            self.current_message = message
        else:
            if len(self.current_message) + len(message) + 1 > 70:
                self.current_message += " *More*"
                self.renderer.render_message(self.current_message)
                self.renderer.refresh()
                self.pgc.getch()
                self.current_message = message
            else:
                self.current_message += " " + message
        if permanent:
            self.state.messages.appendleft(message)
 
if __name__=="__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "los":
        c = time.clock()
        l = los.LOS()
        print time.clock() - c
        c = time.clock()
        f = open("los.dat", "w")
        cPickle.dump(l, f)
        f.close()
        print time.clock() - c
    else:        
        prl = Prl()
        prl.main()
